//! Command execution functionality

use crate::command::Command;
use crate::error::{Result, ShellError};
use serde::{Deserialize, Serialize};
use std::process::{Output, Stdio};
use std::time::{Duration, Instant};
use tokio::process::Command as TokioCommand;
use tokio::time::timeout;

/// Result of command execution
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutionResult {
    /// Exit status code
    pub exit_code: Option<i32>,
    /// Standard output
    pub stdout: String,
    /// Standard error
    pub stderr: String,
    /// Execution duration
    pub duration: Duration,
    /// Whether the command was successful
    pub success: bool,
}

impl ExecutionResult {
    /// Create a new execution result
    pub fn new(
        exit_code: Option<i32>,
        stdout: String,
        stderr: String,
        duration: Duration,
    ) -> Self {
        let success = exit_code.map_or(false, |code| code == 0);
        Self {
            exit_code,
            stdout,
            stderr,
            duration,
            success,
        }
    }
    
    /// Check if the command execution was successful
    pub fn is_success(&self) -> bool {
        self.success
    }
    
    /// Get the exit code, returning an error if the command failed
    pub fn exit_code(&self) -> Result<i32> {
        match self.exit_code {
            Some(code) if code == 0 => Ok(code),
            Some(code) => Err(ShellError::command_failed(
                format!("Command failed with exit code: {}", code)
            )),
            None => Err(ShellError::command_failed("Command terminated by signal")),
        }
    }
    
    /// Get stdout as string, trimming whitespace
    pub fn stdout_trimmed(&self) -> &str {
        self.stdout.trim()
    }
    
    /// Get stderr as string, trimming whitespace
    pub fn stderr_trimmed(&self) -> &str {
        self.stderr.trim()
    }
}

/// Command executor with different execution strategies
#[derive(Debug, Clone)]
pub struct Executor {
    /// Default timeout for commands
    pub default_timeout: Option<Duration>,
    /// Whether to log command execution
    pub log_commands: bool,
    /// Maximum number of concurrent executions
    pub max_concurrent: usize,
}

impl Executor {
    /// Create a new executor with default settings
    pub fn new() -> Self {
        Self {
            default_timeout: Some(Duration::from_secs(30)),
            log_commands: true,
            max_concurrent: 10,
        }
    }
    
    /// Set default timeout
    pub fn with_default_timeout(mut self, timeout: Option<Duration>) -> Self {
        self.default_timeout = timeout;
        self
    }
    
    /// Set whether to log commands
    pub fn with_logging(mut self, log: bool) -> Self {
        self.log_commands = log;
        self
    }
    
    /// Set maximum concurrent executions
    pub fn with_max_concurrent(mut self, max: usize) -> Self {
        self.max_concurrent = max;
        self
    }
    
    /// Execute a command asynchronously
    pub async fn execute(&self, command: &Command) -> Result<ExecutionResult> {
        command.validate()?;
        
        if self.log_commands {
            log::info!("Executing command: {}", command.command_line());
        }
        
        let start_time = Instant::now();
        
        let mut tokio_cmd = TokioCommand::new(&command.program);
        tokio_cmd.args(&command.args);
        
        // Set working directory
        if let Some(ref dir) = command.working_dir {
            tokio_cmd.current_dir(dir);
        }
        
        // Set environment variables
        if command.inherit_env {
            tokio_cmd.envs(&command.env);
        } else {
            tokio_cmd.env_clear();
            tokio_cmd.envs(&command.env);
        }
        
        // Configure stdio
        if command.capture_stdout {
            tokio_cmd.stdout(Stdio::piped());
        }
        if command.capture_stderr {
            tokio_cmd.stderr(Stdio::piped());
        }
        
        // Execute with timeout
        let timeout_duration = command.timeout
            .or(self.default_timeout)
            .unwrap_or(Duration::from_secs(u64::MAX));
            
        let result = timeout(timeout_duration, tokio_cmd.output()).await;
        
        let duration = start_time.elapsed();
        
        match result {
            Ok(Ok(output)) => {
                let execution_result = self.process_output(output, duration);
                
                if self.log_commands {
                    if execution_result.is_success() {
                        log::info!("Command completed successfully in {:?}", duration);
                    } else {
                        log::warn!(
                            "Command failed with exit code {:?} in {:?}", 
                            execution_result.exit_code, 
                            duration
                        );
                    }
                }
                
                Ok(execution_result)
            }
            Ok(Err(e)) => Err(ShellError::Io(e)),
            Err(_) => Err(ShellError::Timeout),
        }
    }
    
    /// Execute a command synchronously (blocking)
    pub fn execute_blocking(&self, command: &Command) -> Result<ExecutionResult> {
        let rt = tokio::runtime::Runtime::new()
            .map_err(|e| ShellError::Unknown(format!("Failed to create runtime: {}", e)))?;
        rt.block_on(self.execute(command))
    }
    
    /// Execute multiple commands concurrently
    pub async fn execute_concurrent(&self, commands: Vec<Command>) -> Vec<Result<ExecutionResult>> {
        let semaphore = std::sync::Arc::new(tokio::sync::Semaphore::new(self.max_concurrent));
        
        let tasks: Vec<_> = commands
            .into_iter()
            .map(|cmd| {
                let executor = self.clone();
                let sem = semaphore.clone();
                tokio::spawn(async move {
                    let _permit = sem.acquire().await.unwrap();
                    executor.execute(&cmd).await
                })
            })
            .collect();
        
        let mut results = Vec::new();
        for task in tasks {
            match task.await {
                Ok(result) => results.push(result),
                Err(e) => results.push(Err(ShellError::Unknown(format!("Task failed: {}", e)))),
            }
        }
        
        results
    }
    
    /// Process command output into ExecutionResult
    fn process_output(&self, output: Output, duration: Duration) -> ExecutionResult {
        let stdout = String::from_utf8_lossy(&output.stdout).to_string();
        let stderr = String::from_utf8_lossy(&output.stderr).to_string();
        let exit_code = output.status.code();
        
        ExecutionResult::new(exit_code, stdout, stderr, duration)
    }
}

impl Default for Executor {
    fn default() -> Self {
        Self::new()
    }
}

/// Convenience functions for quick command execution
pub mod quick {
    use super::*;
    
    /// Execute a simple command and return the result
    pub async fn run<S: Into<String>>(program: S) -> Result<ExecutionResult> {
        let command = Command::new(program);
        let executor = Executor::new();
        executor.execute(&command).await
    }
    
    /// Execute a command with arguments
    pub async fn run_with_args<S, I, A>(program: S, args: I) -> Result<ExecutionResult>
    where
        S: Into<String>,
        I: IntoIterator<Item = A>,
        A: Into<String>,
    {
        let command = Command::new(program).args(args);
        let executor = Executor::new();
        executor.execute(&command).await
    }
    
    /// Execute a command and return only stdout
    pub async fn output<S: Into<String>>(program: S) -> Result<String> {
        let result = run(program).await?;
        if result.is_success() {
            Ok(result.stdout_trimmed().to_string())
        } else {
            Err(ShellError::command_failed(result.stderr_trimmed()))
        }
    }
}